﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Serialization;
using Debug;
using Kristall.Engine;
using Kristall.MyGame.Weapons;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Kristall
{
    //[XmlInclude(typeof(PlasmaLauncher))]
    //[XmlInclude(typeof(CheckPoint))]
    //[XmlInclude(typeof(CheckPointScript))]
    //[XmlInclude(typeof(DisappearScript))]
    //[XmlInclude(typeof(TipNPC))]
    //[XmlInclude(typeof(TestEnemy))]
    //[XmlInclude(typeof(TriggerScript))]
    //[XmlInclude(typeof(Crystal))]
    //[XmlInclude(typeof(PlatformScript))]
    //[XmlInclude(typeof(LavaScript))]
    //[XmlInclude(typeof(FlyingEnemy))]
    //[XmlInclude(typeof(CrystalDrainerEnemy))]
    //[XmlInclude(typeof(DoorScript))]
    public class Level
    {
        #region Fields And Properties

        [XmlIgnore]
        public ContentManager Content;

        public static string FullLevelsPath
        {
            get
            {
                return Application.StartupPath + Kristall.DS + "Content" + Kristall.DS + "Levels";
            }
        }

        public List<Animatable> Animatables
        {
            get;
            set;
        }

        public List<NPC> NPCs
        {
            get;
            set;
        }

        public string FileName
        {
            get;
            set;
        }

        public AABB WinTrigger;

        [XmlIgnore]
        public Player Player;

        [XmlIgnore]
        public List<Projectile> EnemyProjectiles;

        [XmlIgnore]
        public List<Projectile> PlayerProjectiles;

        public List<Light> Lights = new List<Light>();

        [XmlIgnore]
        public List<Light> DynamicLights = new List<Light>();

        [XmlIgnore]
        public Dictionary<string, Texture2D> SpriteTextures;

        [XmlIgnore]
        RenderTarget2D lightsTarget;

        [XmlIgnore]
        RenderTarget2D fogTarget;

        [XmlIgnore]
        private Texture2D backgroundTexture;

        public string BackgroundTexture { get; set; }

        public List<Crystal> Crystals;

        public Vector2 StartPosition { get; set; }

        bool ambientIsPlaying = false;

        [XmlIgnore]
        public List<AABB> AllEntities
        {
            get
            {
                List<AABB> all = new List<AABB>();
                all.AddRange(Lights);
                all.AddRange(NPCs);
                all.AddRange(Animatables);
                all.AddRange(Crystals);
                all.Add(WinTrigger);
                return all;
            }
        }

        [XmlIgnore]
        public Dictionary<string, Texture2D> lightTextures = new Dictionary<string, Texture2D>();

        #endregion

        public Level()
        {
            Animatables = new List<Animatable>();
            EnemyProjectiles = new List<Projectile>();
            PlayerProjectiles = new List<Projectile>();
            NPCs = new List<NPC>();
            SpriteTextures = new Dictionary<string, Texture2D>();
            Crystals = new List<Crystal>();

            FileName = "Unsaved Level";
            WinTrigger = new AABB(100, 100, 100, 100);
            WinTrigger.Id = "WinTrigger";

            BackgroundTexture = @"Backgrounds\Cave";
            StartPosition = new Vector2(100, 100);
        }

        public AABB FindEntityWithId(string s)
        {
            return AllEntities.FirstOrDefault(x => x.Id.Equals(s));
        }

        #region Load And Unload Content

        public void LoadContent()
        {
            if (Content == null)
            {
                Content = new ContentManager(Kristall.ScreenManager.Game.Services, "Content");
            }

            Player = new Player();
            Player.LoadContent(this);
            Player.CheckPoint = StartPosition;
            Player.Respawn();

            NPCs.ForEach(x => x.LoadContent(this));

            Animatables.ForEach(x => x.LoadContent(this));

            Crystals.ForEach(x => x.LoadContent(this));

            Lights.ForEach(x => x.LoadContent(this));

            Kristall.Console.RegisterCommand("print_sprites", "Prints a list of loaded sprites.", delegate(IDebugCommandHost host, string command, IList<string> args)
            {
                SpriteTextures.Keys.ToList().ForEach(x => Kristall.Console.Echo(x));
            });

            Kristall.Console.RegisterCommand("give_plasma", "Give the plasma gun to the player", delegate(IDebugCommandHost host, string command, IList<string> args)
            {
                PlasmaLauncher pl = new PlasmaLauncher();
                pl.LoadContent(this);
                Player.CurrentWeapon = pl;
            });

            Kristall.Console.RegisterCommand("god", "Set Player to God mode", delegate(IDebugCommandHost host, string command, IList<string> args)
            {
                if (Player.Health > 1)
                    Player.Health = 1f;
                else
                    Player.Health = float.MaxValue;
            });

            lightTextures.Add("Radial", Content.Load<Texture2D>(@"Lights\Radial"));
            lightTextures.Add("Crystal", Content.Load<Texture2D>(@"Lights\Crystal1"));
            
            backgroundTexture = Content.Load<Texture2D>(BackgroundTexture);

            lightsTarget = new RenderTarget2D(Kristall.ScreenManager.GraphicsDevice, (int)Kristall.Viewport.X, (int)Kristall.Viewport.Y);
            fogTarget = new RenderTarget2D(Kristall.ScreenManager.GraphicsDevice, (int)Kristall.Viewport.X, (int)Kristall.Viewport.Y);
        }

        public void UnloadContent()
        {
            //Content.Unload();
            Kristall.SoundManager.StopAll("Global");
        }

        #endregion

        #region Update

        public void Update(float dt)
        {
            if (!ambientIsPlaying)
            {
                Kristall.SoundManager.PlayNonInstancedSound("Ambient");
                ambientIsPlaying = true;
            }

            Player.Update(dt);
            NPCs.ForEach(x => x.Update(dt));
            Animatables.ForEach(x => x.Update(dt));
            EnemyProjectiles.ForEach(x => x.Update(dt));
            PlayerProjectiles.ForEach(x => x.Update(dt));
            Crystals.ForEach(x => x.Update(dt));

            for (int i = Animatables.Count - 1; i >= 0; i-- )
            {
                Animatable animatable = Animatables[i];
                //Update player collision
                CollisionEventArgs collision = animatable.Collides(Player);

                
                if (collision != null)
                {
                    animatable.OnCollide(collision);
                    
                    if (animatable.CheckPlayerCollision == false)
                    {
                        continue;
                    }

                    collision.Other = animatable;
                    Player.OnCollide(collision);
                }

                if (animatable.CheckPlayerCollision == false)
                {
                    continue;
                }

                //Update npc collision
                foreach (var npc in NPCs)
                {
                    collision = animatable.Collides(npc);

                    if (collision != null)
                    {
                        if (npc.CheckWorldCollision)
                        {
                            collision.Other = animatable;
                            npc.OnCollide(collision);
                        }
                    }
                }

                //Update projectile collision
                foreach (var projectile in EnemyProjectiles)
                {
                    if (projectile.Bounds.Intersects(animatable.Bounds))
                    {
                        projectile.IsAlive = false;
                    }
                }

                //Update projectile collision
                foreach (var projectile in PlayerProjectiles)
                {
                    if (projectile.Bounds.Intersects(animatable.Bounds))
                    {
                        projectile.IsAlive = false;
                    }
                }
            }

            //Check if the player was hit
            foreach (var projectile in EnemyProjectiles)
            {
                if (Player.Bounds.Intersects(projectile.Bounds))
                {
                    projectile.CollisionWithActor(Player);
                    projectile.IsAlive = false;
                }
            }

            //Check if the enemies was hit
            foreach (var projectile in PlayerProjectiles)
            {
                for (int i = NPCs.Count - 1; i >= 0; i--)
                {
                    if (NPCs[i].Bounds.Intersects(projectile.Bounds))
                    {
                        projectile.CollisionWithActor(NPCs[i]);
                    }
                }
            }


            //Check if player collided with eniemies
            foreach (var enemy in NPCs)
            {
                CollisionEventArgs collision = enemy.Collides(Player);
                if(collision != null)
                {
                    collision.Resolve = false;
                    enemy.OnCollide(collision);
                }
            }
        }

        #endregion

        #region Draw

        public void Draw(SpriteBatch batch, Camera2D camera)
        {
            GraphicsDevice GraphicsDevice = Kristall.ScreenManager.GraphicsDevice;

            Rectangle screen = GraphicsDevice.Viewport.Bounds;

            //Draw lights to new render target
            DrawLights(batch, camera, GraphicsDevice);

            //Draw forg of war
            DrawFog(batch, camera, GraphicsDevice);

            GraphicsDevice.Clear(Color.Black);

            batch.Begin(); //SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.LinearWrap, DepthStencilState.Default, RasterizerState.CullNone);
            if(!backgroundTexture.IsDisposed)
                batch.Draw(backgroundTexture, screen, null, Color.White);
            batch.End();

            batch.Begin(SpriteSortMode.BackToFront, null, SamplerState.LinearWrap, null, null, null, camera.World);

            Animatables.ForEach(x => x.Draw(batch));

            Crystals.ForEach(x => x.Draw(batch));

            NPCs.ForEach(x => x.Draw(batch));

            Player.Draw(batch);

            EnemyProjectiles.ForEach(x => x.Draw(batch));
            PlayerProjectiles.ForEach(x => x.Draw(batch));

            //Draw starting location & win trigger
            if (Kristall.EnableDebug)
            {
                //StartLocation.DrawDebug(batch, Color.Green);
                WinTrigger.DrawDebug(batch, Color.Red);
            }

            batch.End();

            BlendState blendStateFog = new BlendState();
            blendStateFog.AlphaSourceBlend = Blend.Zero; //Kristall.Options.asb; //Zero
            blendStateFog.AlphaDestinationBlend = Blend.Zero; // Kristall.Options.adb; //Zero
            blendStateFog.ColorSourceBlend = Blend.DestinationColor; // Kristall.Options.csb; //DestinationColor
            blendStateFog.ColorDestinationBlend = Blend.Zero; // Kristall.Options.cdb; //Zero

            batch.Begin(SpriteSortMode.Immediate, blendStateFog);
            batch.Draw(fogTarget, Vector2.Zero, Color.White);
            batch.End();

            if (Kristall.Options.lights)
            { 
                BlendState blendState = new BlendState();

                blendState.AlphaSourceBlend = Blend.SourceAlpha;
                blendState.AlphaDestinationBlend = Blend.DestinationAlpha;
                blendState.ColorSourceBlend =  Kristall.Options.source_blend;
                blendState.ColorDestinationBlend = Kristall.Options.destination_blend;

                //draw the light's rendertargets texture over the entire screen
                batch.Begin(SpriteSortMode.Immediate, blendState);
                batch.Draw(lightsTarget, Vector2.Zero, Color.White);
                batch.End();
            }
        }

        private void DrawFog(SpriteBatch batch, Camera2D camera, GraphicsDevice GraphicsDevice)
        {
            GraphicsDevice.SetRenderTarget(fogTarget);

            GraphicsDevice.Clear(Color.White * Kristall.Options.fl_ambient);
            
            batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, camera.World);

            Player.FollowLight.Draw(batch, lightTextures["Radial"]);

            batch.End();

            if (Kristall.Options.fl_drawlights)
            {
                batch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
                batch.Draw(lightsTarget, Vector2.Zero, Color.White);
                batch.End();
            }

            GraphicsDevice.SetRenderTarget(null);
        }

        private void DrawLights(SpriteBatch batch, Camera2D camera, GraphicsDevice GraphicsDevice)
        {
            GraphicsDevice.SetRenderTarget(lightsTarget);

            GraphicsDevice.Clear(Color.White * Kristall.Options.ambient_light);

            batch.Begin(SpriteSortMode.Deferred, BlendState.Additive, null, null, null, null, camera.World);

            if (!lightTextures["Radial"].IsDisposed)
            {
                Lights.ForEach(x => x.Draw(batch, lightTextures[x.Texture]));
                DynamicLights.ForEach(x => x.Draw(batch, lightTextures[x.Texture]));
            }

            batch.End();

            //reset render target and draw everything as usual
            GraphicsDevice.SetRenderTarget(null);
        }

        #endregion

        #region Save/Load

        /// <summary>
        /// 
        /// </summary>
        /// <param name="levelName"></param>
        /// <returns>true on success, false otherwise</returns>
        public bool Save(string levelName)
        {
            Kristall.Echo(DebugConsole.CCCyan + "Saving: " + levelName);

            this.FileName = Path.GetFileName(levelName);

            string tempName = "temp.lvl";

            XmlSerializer serializer = new XmlSerializer(typeof(Level), Kristall.GetExtraTypes());
            FileStream fs = new FileStream(tempName, FileMode.Create);

            bool success = false;

            try
            {
                serializer.Serialize(fs, this);
                success = true;
            }
            catch (Exception e)
            {
                MessageBox.Show("Error saving level: " + e.Message + " Inner: " + e.InnerException.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                fs.Close();

                if (success)
                {
                    //If save was successfull, delete old file and move the temp file
                    File.Delete(levelName);
                    File.Move(tempName, levelName);
                }
                else
                { 
                    //delete temp file
                    File.Delete(tempName);
                }
            }

            return success;
        }

        public static Level Load(string levelName)
        {
            Kristall.Echo(DebugConsole.CCCyan + "Loading: " + DebugConsole.CCWhite + Path.GetFileName(levelName));

            if (File.Exists(levelName))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Level), Kristall.GetExtraTypes());

                FileStream fs = new FileStream(levelName, FileMode.Open);

                Level tempLevel = null;

                try
                {
                    tempLevel = serializer.Deserialize(fs) as Level;
                }
                catch (Exception e)
                {
                    Kristall.Echo(DebugConsole.CCRed + e.Message);
                    MessageBox.Show("Error loading level " + Path.GetFileName(levelName), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    fs.Close();
                }

                return tempLevel;
            }
            else
            {
                throw new Exception("Level " + levelName + " was not found!");
            }
        }

        #endregion
    }
}
